package avicit.bdp.dlm.service;

import avicit.bdp.common.base.BaseService;
import avicit.bdp.common.datasource.BaseDataSource;
import avicit.bdp.common.dto.NodeInfo;
import avicit.bdp.common.dto.NodeStorageDetail;
import avicit.bdp.common.service.dto.CommonTypeDTO;
import avicit.bdp.common.service.dto.DataSourceDTO;
import avicit.bdp.common.service.service.CommonTypeService;
import avicit.bdp.common.utils.BdpLogUtil;
import avicit.bdp.common.utils.ConfigUtils;
import avicit.bdp.common.utils.DateUtils;
import avicit.bdp.common.utils.NumberUtils;
import avicit.bdp.common.utils.database.DBUtils;
import avicit.bdp.common.utils.database.adapter.DbAdapter;
import avicit.bdp.common.utils.database.adapter.DbAdapterUtils;
import avicit.bdp.common.utils.enums.CommonTypeEnums;
import avicit.bdp.core.constant.Constants;
import avicit.bdp.core.file.util.FileUtils;
import avicit.bdp.dlm.dao.DlmStatDao;
import avicit.bdp.dlm.dto.DlmDataSourceDTO;
import avicit.bdp.dlm.dto.DlmStatDTO;
import avicit.bdp.core.enums.Dimension;
import avicit.bdp.dlm.vo.DataSourceBaseInfoVo;
import avicit.bdp.dlm.vo.DataSourceViewVo;
import avicit.bdp.dlm.vo.StateLineChartDataVo;
import avicit.bdp.dlm.vo.StateOverviewVo;
import avicit.bdp.dlm.vo.StorageInfoVo;
import avicit.platform6.core.exception.BusinessException;
import avicit.platform6.core.properties.PlatformConstant;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;

/**
 * 统计服务
 *
 * @author xugb
 * @date 2020/12/1 9:22
 */
@Service
public class DlmStatService extends BaseService<DlmStatDao, DlmStatDTO> {

    @Autowired
    private DlmDataSourceService dlmDataSourceService;

    @Autowired
    private CommonTypeService commonTypeService;

    @Autowired
    private DlmHiveStatService dlmHiveStatService;

    /**
     * 预估可用天数KEy
     */
    private static final String AVAILABLE_DAYS_LIMIT = "available_days_limit";

    /**
     * 概览基本信息
     *
     * @param typeId    分类ID
     * @param type      类型 0--结构化，1--非结构化，-1--分类
     * @param dimension 统计维度 [week,month,year,selfDefine]
     *                  week:表示近7天
     *                  month:表示近30天
     *                  year:表示近1年
     *                  userDefine：自定义时间段，开始时间和结束时间值不能为空
     * @return 数据湖概览VO数据
     */
    public List<StateOverviewVo> overview(String typeId, Integer type, String dimension) {
        if (type == null) {
            throw new BusinessException("类型不能为空");
        }
        List<StateOverviewVo> result = new ArrayList<>();

        //按照分类统计
        if (Constants.TYPE == type) {
            if (StringUtils.isEmpty(typeId)) {
                CommonTypeDTO commonTypeDTO = commonTypeService.selectRootByType(CommonTypeEnums.STORAGE_TYPE);
                if (commonTypeDTO != null) {
                    getStateOverViewByTypeId(commonTypeDTO.getId(), dimension, result);
                }
            } else {
                getStateOverViewByTypeId(typeId, dimension, result);
            }
        } else if (Constants.STRUCTURE == type || Constants.UN_STRUCTURE == type) {
            //按统计结构化和非结构化
            StateOverviewVo stateOverviewVo = getStateOverviewByStructure(type, dimension);
            result.add(stateOverviewVo);
        } else if (Constants.ALL == type) {
            StateOverviewVo stateOverviewVo = getStateOverviewAll();
            result.add(stateOverviewVo);
        }

        String logTitle = "查看存储统计";
        BdpLogUtil.log("存储统计模块", logTitle, PlatformConstant.OpType.select);
        return result;
    }


    /**
     * 按照分类查询概览
     *
     * @param typeId    分类ID
     *                  1.分类ID为空，查询当前统计里面所有分类，用于数据概览页面分页展示
     *                  2.分类ID不为空，查询当前分类子类，用于数据查看页面统计当前分类子分类数据
     *                  分类ID中包含"-",表示当前分类为虚拟分类节点，子节点下挂载数据源节点
     *                  分类ID中不包含"-",表示当前分类下挂载分类节点
     * @param dimension 统计维度 [week,month,year,selfDefine]
     *                  week:表示近7天
     *                  month:表示近30天
     *                  year:表示近1年
     *                  userDefine：自定义时间段，开始时间和结束时间值不能为空
     * @param result    返回结果集合
     */
    private void getStateOverViewByTypeId(String typeId, String dimension, List<StateOverviewVo> result) {
        List<String> typeIdList;

        if (typeId.contains(Constants.SUBTRACT_STRING)) {
            //分类ID中包含"-",表示当前分类为虚拟分类节点，子节点下挂载数据源节点
            String[] typeIdAndTypeArr = typeId.split(Constants.SUBTRACT_STRING);
            typeId = typeIdAndTypeArr[0];
            Integer type = Integer.valueOf(typeIdAndTypeArr[1]);
            handleStateOverviewByDataSource(typeId, dimension, result, type);
            return;
        } else {
            // 当前分类下的一级子节点
            typeIdList = commonTypeService.selectCommonTypeByParentId(typeId);
            //查询子节点如果为空，可能子节点为空或者挂载虚拟节点
            if (CollectionUtils.isEmpty(typeIdList)) {
                handleStateOverviewByDataSource(typeId, dimension, result, null);
                return;
            }
        }

        //按照子分类ID聚合查询
        if (CollectionUtils.isNotEmpty(typeIdList)) {
            Map<String, String> typeNameMap = commonTypeService.getTypeNameMap(typeIdList);
            for (String subTypeId : typeIdList) {
                List<String> allSubTypeIdList = getAllSubTypeIdList(subTypeId);
                StateOverviewVo stateOverviewVo = new StateOverviewVo();
                stateOverviewVo.setTitle(typeNameMap.get(subTypeId));
                stateOverviewVo.setTypeId(subTypeId);
                if (CollectionUtils.isNotEmpty(allSubTypeIdList)) {
                    StateLineChartDataVo lineChartData = lineChart(null, allSubTypeIdList, dimension, null, null, null);
                    stateOverviewVo.setLineChartData(lineChartData);
                    StorageInfoVo storageInfo = getStorageInfo(allSubTypeIdList, null, null);
                    storageInfo.setDataSourceNum(dlmDataSourceService.countLakeDataSourceByType(allSubTypeIdList));
                    stateOverviewVo.setStorageInfo(storageInfo);
                }
                result.add(stateOverviewVo);
            }
        }
    }

    private void handleStateOverviewByDataSource(String typeId, String dimension, List<StateOverviewVo> result, Integer type) {
        List<DataSourceDTO> dataSourceList = dlmDataSourceService.getDataSourceListByTypeIdAndType(typeId, type);
        for (DataSourceDTO dataSourceDTO : dataSourceList) {
            StateLineChartDataVo lineChartData = lineChart(null, null, dimension, dataSourceDTO.getId(), null, null);
            StateOverviewVo stateOverviewVo = new StateOverviewVo();
            stateOverviewVo.setTitle(dataSourceDTO.getDatasourceName());
            stateOverviewVo.setTypeId(dataSourceDTO.getTypeId());
            stateOverviewVo.setDataSourceId(dataSourceDTO.getId());
            stateOverviewVo.setLineChartData(lineChartData);
            StorageInfoVo storageInfo = getStorageInfo(null, dataSourceDTO.getId(), null);
            stateOverviewVo.setStorageInfo(storageInfo);
            storageInfo.setDataSourceNum(1);
            result.add(stateOverviewVo);
        }
    }


    /**
     * 查询结构化和非结构化概览数据
     *
     * @param type      类型 0--结构化，1--非结构化，-1--分类
     * @param dimension 统计维度 [week,month,year,selfDefine]
     *                  week:表示近7天
     *                  month:表示近30天
     *                  year:表示近1年
     *                  userDefine：自定义时间段，开始时间和结束时间值不能为空
     * @return 概览数据
     */
    private StateOverviewVo getStateOverviewByStructure(Integer type, String dimension) {
        StateOverviewVo stateOverviewVo = new StateOverviewVo();
        stateOverviewVo.setTitle(type == Constants.STRUCTURE ? "结构化存储" : "非结构化存储");
        //查询图表数据
        StateLineChartDataVo lineChartData = lineChart(type, null, dimension, null, null, null);
        stateOverviewVo.setLineChartData(lineChartData);
        //查询存储数据
        StorageInfoVo storageInfo = getStorageInfo(null, null, type);
        stateOverviewVo.setStorageInfo(storageInfo);
        storageInfo.setDataSourceNum(dlmDataSourceService.countLakeDataSourceByStructure(type));
        return stateOverviewVo;
    }

    /**
     * 统计所有
     *
     * @return 概览数据
     */
    private StateOverviewVo getStateOverviewAll() {
        StateOverviewVo stateOverviewVo = new StateOverviewVo();
        stateOverviewVo.setTitle("概览");
        //查询存储数据
        StorageInfoVo storageInfo = getStorageInfo(null, null, null);
        stateOverviewVo.setStorageInfo(storageInfo);
        storageInfo.setDataSourceNum(dlmDataSourceService.countLakeDataSourceByStructure(null));
        return stateOverviewVo;
    }


    /**
     * 数据湖大屏数据
     * 数据查询时间默认为近30天
     *
     * @param type      类型 0--结构化，1--非结构化，-1--分类
     * @param dimension 统计维度 [week,month,year,userDefine]
     *                  week:表示近7天
     *                  month:表示近30天
     *                  year:表示近1年
     *                  userDefine：自定义时间段，开始时间和结束时间值不能为空
     * @return 数据湖概览VO数据
     */
    public StateOverviewVo bigScreen(String dimension, Integer type) {
        //按统计结构化和非结构化
        StateOverviewVo stateOverviewVo = getStateOverviewByStructure(type, dimension);
        StorageInfoVo storageInfo = stateOverviewVo.getStorageInfo();
        return stateOverviewVo;
    }


    /**
     * 概览图表数据
     *
     * @param type             类型 0--结构化，1--非结构化，-1--分类
     * @param allSubTypeIdList 分类ID下所有子分类
     * @param dimension        统计维度 [week,month,year,selfDefine]
     *                         week:表示近7天
     *                         month:表示近30天
     *                         year:表示近1年
     *                         userDefine：自定义时间段，开始时间和结束时间值不能为空
     * @param dataSourceId     数据源ID
     * @param startTime        开始日期
     * @param endTime          结束日期
     *                         按天统计：DATE_FORMAT(start_time,'%Y%m%d')
     *                         按周统计：DATE_FORMAT(start_time,'%Y%u')
     *                         按月统计：DATE_FORMAT(start_time,'%Y%m')
     * @return 数据湖概览图表数据
     */
    public StateLineChartDataVo lineChart(Integer type, List<String> allSubTypeIdList, String dimension,
                                          String dataSourceId, String startTime, String endTime) {

        Dimension dimensionByName = Dimension.getDimensionByName(dimension);
        if (dimensionByName == null) {
            throw new BusinessException("请检查统计维度参数");
        }

        if (StringUtils.isNotBlank(dataSourceId) || CollectionUtils.isNotEmpty(allSubTypeIdList)) {
            type = null;
        }

        StateLineChartDataVo dataVo = new StateLineChartDataVo();
        //赋值日期值
        if (Dimension.USER_DEFINE != dimensionByName) {
            startTime = DateUtils.getDayNumBeforeToday(dimensionByName.getDays());
            endTime = DateUtils.getDayNumBeforeToday(Dimension.DAY.getDays());
        } else {
            if (StringUtils.isAnyBlank(startTime, endTime)) {
                throw new BusinessException("请选择时间");
            }
        }

        //计算开始时间和结束时间的间隔天数
        List<String> dateList;
        //通过维度和统计时间计算数据库日期统计格式
        String pattern = Dimension.DAY.getPattern();
        if (Dimension.YEAR == dimensionByName) {
            //选择近1年，按照月统计
            pattern = Dimension.MONTH.getPattern();
            dateList = DateUtils.getAllMonths(startTime, endTime);
        } else {
            dateList = DateUtils.getAllDays(startTime, endTime);
        }

        List<Double> amountList = new ArrayList<>();
        List<Double> dayAmountList = new ArrayList<>();
        for (int i = 0, j = dateList.size(); i < j; i++) {
            amountList.add(null);
            dayAmountList.add(null);
        }

        Integer endIndex = 10;
        if (Dimension.WEEK == dimensionByName) {
            endIndex = 10;
        }else if (Dimension.MONTH == dimensionByName) {
            endIndex = 10;
        }else if (Dimension.YEAR == dimensionByName) {
            endIndex = 7;
        }
        List<DlmStatDTO> stateLineChartData = mapper.getStateLineChartData(pattern, allSubTypeIdList,
                dataSourceId, type, startTime, endTime, endIndex);
        if (CollectionUtils.isNotEmpty(stateLineChartData)) {
            long maxTotalAmount = 0L;
            long maxDayAmount = 0L;
            //查找坐标轴最大值，确定坐标轴单位
            for (DlmStatDTO dlmStatDTO : stateLineChartData) {
                maxTotalAmount = Math.max(Math.abs(dlmStatDTO.getTotalAmount()), maxTotalAmount);
                maxDayAmount = Math.max(Math.abs(dlmStatDTO.getDayAmount()), maxDayAmount);
            }

            String amountUnit = FileUtils.formatNumber(maxTotalAmount);
            dataVo.setAmountUnit(amountUnit);
            String dayAmountUnit = FileUtils.formatNumber(maxDayAmount);
            dataVo.setDayAmountUnit(dayAmountUnit);

            for (DlmStatDTO dlmStatDTO : stateLineChartData) {
                String statDate = dlmStatDTO.getStatDate();
                int index = dateList.indexOf(statDate);
                Long totalAmount = dlmStatDTO.getTotalAmount();
                amountList.set(index, FileUtils.formatAmountUnit(amountUnit, totalAmount));
                Long dayAmount = dlmStatDTO.getDayAmount();
                dayAmountList.set(index, FileUtils.formatAmountUnit(dayAmountUnit, dayAmount));
            }
        }

        if (Dimension.YEAR != dimensionByName) {
            List<String> list = new ArrayList<>();
            for (String date : dateList) {
                list.add(date.substring(date.indexOf(Constants.SUBTRACT_STRING) + 1));
            }
            dataVo.setDateList(list);
        } else {
            dataVo.setDateList(dateList);
        }
        dataVo.setAmountList(amountList);
        dataVo.setDayAmountList(dayAmountList);
        return dataVo;
    }


    /**
     * 存储查看
     *
     * @param dataSourceId 数据源ID
     * @param typeId       分类ID
     * @return 存储查看VO
     */
    public DataSourceViewVo dataSourceView(String typeId, String dataSourceId) {

        if (StringUtils.isAnyBlank(typeId, dataSourceId)) {
            throw new BusinessException("分类ID或数据源ID不能为空");
        }
        DataSourceViewVo dataSourceViewVo = new DataSourceViewVo();

        dataSourceViewVo.setStorageInfo(getStorageInfo(null, dataSourceId, null));

        DlmDataSourceDTO dataSource = dlmDataSourceService.selectDataSource(dataSourceId, typeId);
        if (dataSource != null) {
            DataSourceBaseInfoVo baseInfoVo = new DataSourceBaseInfoVo();
            dataSourceViewVo.setBaseInfo(baseInfoVo);
            DataSourceDTO dataSourceDTO = dataSource.getDataSourceDTO();
            baseInfoVo.setVersion(dataSource.getDataSourceVersion());

            if (dataSourceDTO != null) {
                baseInfoVo.setName(dataSourceDTO.getDatasourceName());
                baseInfoVo.setType(dataSourceDTO.getDatasourceType());

                DbAdapter dbAdapter = DbAdapterUtils.getDbAdapter(dataSourceDTO.getDatasourceType());
                BaseDataSource baseDataSource = DBUtils.getBaseDataSource(dataSourceDTO);
                if (dbAdapter != null && baseDataSource != null) {
                    List<NodeStorageDetail> storageDetails = null;
                    try {
                        if (Constants.DB_TYPE_HIVE.equals(dataSourceDTO.getDatasourceType())) {
                            storageDetails = dlmHiveStatService.getTop5TableAmountList(dataSourceId, DateUtils.getDayNumBeforeToday(1));
                        } else {
                            storageDetails = dbAdapter.getTop5TableAmountList(baseDataSource);
                        }
                    } catch (Exception e) {
                        logger.error("get top5 TableAmount error: {}", e.getMessage());
                    }
                    dataSourceViewVo.setStorageTop5(storageDetails);

                    List<NodeInfo> nodeInfoList = null;
                    try {
                        nodeInfoList = dbAdapter.getNodeInfoList(baseDataSource);
                    } catch (Exception e) {
                        logger.error("get node info error: {}", e.getMessage());
                    }
                    if (CollectionUtils.isNotEmpty(nodeInfoList)) {
                        if (!Constants.DB_TYPE_GREENPLUM.equals(dataSourceDTO.getDatasourceType())
                                && !Constants.DB_TYPE_POSTGRESQL.equals(dataSourceDTO.getDatasourceType())
                                && !Constants.DB_TYPE_HDFS.equals(dataSourceDTO.getDatasourceType())) {
                            StorageInfoVo storageInfoVo = dataSourceViewVo.getStorageInfo();
                            NodeInfo nodeInfo = nodeInfoList.get(0);
                            nodeInfo.setUsedCapacity(storageInfoVo.getTotalAmountLabel());
                            nodeInfo.setCapacity(storageInfoVo.getAvailableAmountLabel());
                            nodeInfoList.clear();
                            nodeInfoList.add(nodeInfo);
                        }
                        dataSourceViewVo.setNodeInfo(nodeInfoList);
                        baseInfoVo.setNodeNum(nodeInfoList.size());
                    }
                }
            }
        }


        return dataSourceViewVo;
    }

    /**
     * 查询存储信息
     *
     * @param allSubTypeIdList 分类ID下所有子分类
     * @param dataSourceId     数据源ID
     * @param type             类型 0--结构化，1--非结构化，-1--分类
     * @return 存储信息
     */
    private StorageInfoVo getStorageInfo(List<String> allSubTypeIdList, String dataSourceId, Integer type) {
        StorageInfoVo storageInfoVo = new StorageInfoVo();
        String stateDate = DateUtils.getDayNumBeforeToday(1);
        DlmStatDTO storageState = mapper.getStorageState(allSubTypeIdList, dataSourceId, type, stateDate);

        if (storageState != null) {
            storageInfoVo.setObjectCount(storageState.getObjectCount());
            storageInfoVo.setOtherCount(storageState.getOtherCount());

            //总容量
            Long availableAmount = storageState.getAvailableAmount();
            String availableAmountUnit = FileUtils.readableFileUnit(availableAmount);
            int scale = 0;
            if (Constants.TB_UNIT.equals(availableAmountUnit)) {
                scale = 2;
            }
            storageInfoVo.setAvailableAmount(availableAmount);
            storageInfoVo.setAvailableAmountLabel(FileUtils.readableFileSizeWithUnit(availableAmount, scale));

            //已用容量
            Long totalAmount = storageState.getTotalAmount();
            storageInfoVo.setTotalAmount(totalAmount);
            storageInfoVo.setTotalAmountLabel(FileUtils.readableFileSizeWithUnit(totalAmount, 2));
            //已用百分比
            storageInfoVo.setUsagePercent(NumberUtils.divideRateStr(totalAmount, availableAmount) + "%");

            //剩余容量
            long useAbleAmount = Math.max(availableAmount - totalAmount, 0L);
            storageInfoVo.setUsableAmount(useAbleAmount);
            storageInfoVo.setUsableAmountLabel(FileUtils.readableFileSizeWithUnit(useAbleAmount, 2));

            //查询近7天日均增长量
            String latestSevenDays = DateUtils.getDayNumBeforeToday(Calendar.DAY_OF_WEEK);
            long sumDayAmount = 0L;
            long avgDayAmount = 0L;

            List<DlmStatDTO> latestSevenDayStatList = mapper.getStateLineChartData(Dimension.DAY.getPattern(),
                    allSubTypeIdList, dataSourceId, type, latestSevenDays, stateDate, 10);
            if (CollectionUtils.isNotEmpty(latestSevenDayStatList)) {
                for (DlmStatDTO stateLineChartDatum : latestSevenDayStatList) {
                    sumDayAmount += stateLineChartDatum.getDayAmount();
                }
                avgDayAmount = sumDayAmount / latestSevenDayStatList.size();
            }

            storageInfoVo.setAvgIncrementOfRecent7(FileUtils.readableFileSizeWithoutUnit(avgDayAmount, 2));
            storageInfoVo.setAvgIncrementOfRecent7Unit(FileUtils.readableFileUnit(avgDayAmount));

            if (avgDayAmount > 0 && useAbleAmount > 0L) {
                long availableDays = useAbleAmount / avgDayAmount;
                storageInfoVo.setAvailableDays(getAvailableDayStr(availableDays));
                //如果预估可用天数小于15天，前端界面标红
                int availableDaysLimit = ConfigUtils.getInstance().getInteger(AVAILABLE_DAYS_LIMIT, 15);
                storageInfoVo.setOver(availableDays <= availableDaysLimit);
            } else if (useAbleAmount == 0L) {
                storageInfoVo.setAvailableDays("0天");
                storageInfoVo.setOver(true);
            } else {
                storageInfoVo.setAvailableDays("大于1年");
            }
        }

        return storageInfoVo;
    }

    private static String getAvailableDayStr(long availableDays) {
        DecimalFormat df = new DecimalFormat(",###,##0");
        if (availableDays <= Dimension.YEAR.getDays()) {
            BigDecimal bigDecimal = new BigDecimal(String.valueOf(availableDays));
            return df.format(bigDecimal) + "天";
        } else {
            BigDecimal bigDecimal = new BigDecimal(String.valueOf(availableDays / Dimension.YEAR.getDays()));
            return df.format(bigDecimal) + "年";
        }
    }

    /**
     * 获取最新一天的数据
     *
     * @param dataSourceId 数据源ID
     * @return 最新一天的数据
     */
    public Long selectLatestAmount(String dataSourceId) {
        return mapper.selectLatestAmount(dataSourceId);
    }


    /**
     * 查询分类ID下所有的子集
     *
     * @param typeId 当前分类ID
     * @return 分类ID子集
     */
    public List<String> getAllSubTypeIdList(String typeId) {
        //查询当前分类子分类
        List<String> typeList = commonTypeService.getChildrenIds(CommonTypeEnums.STORAGE_TYPE, typeId, "");
        if (typeList == null) {
            typeList = new ArrayList<>();
        }
        typeList.add(typeId);
        return typeList;
    }


    public boolean isRun() {
        String yesterday = DateUtils.getDayNumBeforeToday(1);
        DlmStatDTO dlmStatDTO = new DlmStatDTO();
        dlmStatDTO.setStatDate(yesterday);
        List<DlmStatDTO> list = this.selectList(dlmStatDTO);
        if(CollectionUtils.isNotEmpty(list)){
            return true;
        }
        return false;
    }
}
